"use strict";
var hashTable = require('../common/hashtable.js');

exports.create = function(pObj) { return new FsmManager(pObj); };

var MODNAME = 'FsmManager';

/*This is the main constructor function of FSM manager */
var FsmManager = function (pSubsObj) {
	if (!(this instanceof FsmManager)) return new FsmManager(pSubsObj);
	this._SubsObj= pSubsObj;
	this.logger = pSubsObj.logger;
	this._FsmTable = hashTable.create();
	this._SessionTable = hashTable.create();
};

FsmManager.prototype.register = function(pSubsObj, pUserFsm) {
	this.logger.log(MODNAME, 'INFO_LEVEL_4', '['+pSubsObj._Config.NAME+'] Initializing Fsm Script');
	var fsm = inherit(pUserFsm);  // create fsm instance	
	fsm._FsmManager = this;  // store back pointer to fsm-manager
	
	//Load Namespaces
	var namespaces = pUserFsm.namespaces;
	this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Registering Namespaces...');
	for(var i = 0; i < namespaces.length; i++) {
		var name = namespaces[i];
		this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Namespace: ['+name+']');
		var obj = this._FsmTable.getItem(name);
		if (obj) {
			this.logger.log(MODNAME, 'WARNING','Namespace already registered with this name ['+name+']');
		} else {
			fsm._Events = getFsmEvents();
			this._FsmTable.setItem(name, fsm);
		}
	}
	
	function getFsmEvents(pFsmObj) {
		//TODO:
	}
	
	//////////////////////////////////////////////////object functions//////////////////////////////////
	// to set the new state
	fsm.setState = function(newState, event) {
		var session = this;
		// validate the newState
		if(!newState) {
			this._FsmManager.logger.log(MODNAME, 'WARNING', 'Invalid state ['+newState+']');
			return;
		}
		
		// do cleanup stuff in the curent state
		if(session.state){
			//call exit event of Current StateObject if it is defined
			var currStateObj = session[session.state];
			if(currStateObj){
				var exitFunc = currStateObj["event_exit"];
				this._FsmManager.logger.log(MODNAME, 'INFO_LEVEL_4', '['+session.sessionKey+'] state: '+session.state+', invoked event: event_exit');
				if(exitFunc){					
					exitFunc(this._FsmManager._SubsObj, event, session);
				}
			}
		}
		// exit if the newState is state_exit
		session.state = newState;
		this._FsmManager.logger.log(MODNAME, 'INFO_LEVEL_4', '['+this.sessionKey+'] changed to new state: ['+session.state+']');
		if(newState === "state_exit") {
		    this._FsmManager.cleanupFsmSession(session);
		    return;
		}
		
		// get new stateObject
		var stateObject = session[newState];
		if(!stateObject) {
			this._FsmManager.logger.log(MODNAME, 'WARNING', '['+this.sessionKey+'] new state: ['+session.state+'] is not found in FSM script');
			return;
		}
		// call init event of newstateobject if it is defined
		var entryFunc = stateObject["event_init"];
		this._FsmManager.logger.log(MODNAME, 'INFO_LEVEL_4', '['+session.sessionKey+'] state: '+session.state+', invoked event: event_entry');
		if(entryFunc) {			
			entryFunc(this._FsmManager._SubsObj, event, session);
		}
	
	};
	
	// creates new event object
	fsm.createNewEvent = function (pEventName, pEventType, pPayload, pRespEventName) {
		var eventObj = this._FsmManager._SubsObj.createNewEvent(pEventName, pEventType, pPayload, pRespEventName);		
		return eventObj;
	};
	fsm.createRespEvent = function (pPayload, pRcvdEvent) {
		var payload = pPayload || {};
		var rcvdEvent = pRcvdEvent || this.rcvdEvent;
		return this._FsmManager._SubsObj.createRespEvent(pPayload, rcvdEvent);
	};
	
	fsm.sendInternalEvent = function (userEvent, newState) {
		this.sendEvent(userEvent, newState, '1');
	};
	// send the event
	fsm.sendEvent = function(userEvent, newState, isInternalEvent) {
		var eventType = userEvent.getEventType();
		if (isInternalEvent && isInternalEvent === '1') {
			if(eventType === 'REQUEST') {
				userEvent.reqFsmSessionId = this.sessionKey;
			} else if(eventType === 'RESPONSE') {
				
			}
		} else { // in case of external event
			if(eventType === 'REQUEST') {
				userEvent.reqFsmSessionId = this.sessionKey;
			} else if(eventType === 'RESPONSE') {
				
			}
		}		

		//this._FsmManager.logger.log(MODNAME, 'INFO_LEVEL_4', '['+this.sessionKey+'] state: '+this.state+', sending event: '+userEvent.getEventName());
		this._FsmManager._SubsObj.sendEvent(userEvent); // invoke subs sendevent		
		//setTimeout(function() {		
		//}, 1);
		this._FsmManager.logger.log(MODNAME, 'INFO_LEVEL_4', '['+this.sessionKey+'] state: '+this.state+', sentEvent: '+userEvent.getEventName()+', '+JSON.stringify(userEvent));

		// set new state if defined
		if (newState) {
			this.setState(newState, userEvent);
		}

	};
	
	//console.log(fsm);
	return fsm;
};

/**
 * Get Fsm Object from given EventObj
 * @param pEvent
 * @returns FsmObject
 */
FsmManager.prototype.getFsmObjFromEvent = function(pEventName) {
	var eventName = pEventName;
	var index = eventName.indexOf('.');
	var namespace = eventName.substring(0,index);
	var fsmObj = this._FsmTable.getItem(namespace);
	if(fsmObj) {
		this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Retrieved FSM-Object for namespace: ['+namespace+']');
	}
	return fsmObj;
};

/**
 * Creates New Fsm Session with the ginven FsmObject
 * @param pSsnKey
 * @param pParentSsnKey
 * @param pFsmObj
 * @returns Fsm Session Object
 */
FsmManager.prototype.createFsmSession = function(pSsnKey, pParentSsnKey, pFsmObj) {
	var session = inherit(pFsmObj);
	session.parentSessionKey = pParentSsnKey;
	session.sessionKey = pSsnKey;
	session.hasNamespace = function(nameSpace){
		if(this.namespaces.indexOf(nameSpace) >= 0){
			return true;
		}
		return false;
	};
	this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Created FSM-Session [key: '+session.sessionKey+']');
	return session;
};

/**
 * Returns list of sessions where the parentsessionkey is equal to the given input
 * @param pSsnKey
 * @returns list of sessionObjects
 */
FsmManager.prototype.getChildFsmSessions = function(pSsnKey) {
	var childSessionKeys = [];
	var hashKeys = this._SessionTable.keys();
	var len = hashKeys.length;
	for (var i = 0; i < len; i++) {
		var hashObj = this._SessionTable.getItem(hashKeys[i]);
		if (pSsnKey === hashObj.parentSessionKey) {
				childSessionKeys.push(hashObj.sessionKey);
			}
	}
	return childSessionKeys;
};

/**
 * Cleanup session object
 * @param session
 */
FsmManager.prototype.cleanupFsmSession = function(session) {
	var childSessionKeys = this.getChildFsmSessions(session.sessionKey);
	
	for(var i=0; i<childSessionKeys.length; i++) {
		this._SessionTable.removeItem(childSessionKeys[i]);
	}
	
	this._SessionTable.removeItem(session.sessionKey);
	this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Removed FSM-Session [Key:'+session.sessionKey+'] which has ['+childSessionKeys.length+'] childsessions');
	this.printSessionTable();
};

/**
 * Initializes a new session object.
 * @param pEvent
 * @param pSsnObj
 */
FsmManager.prototype.initializeFsmSession = function(pEvent, pSsnObj) {
	this._SessionTable.setItem(pSsnObj.sessionKey, pSsnObj);
	this.logger.log(MODNAME, 'INFO_LEVEL_4', '['+pSsnObj.sessionKey+'] setting new state: state_idle');
	pSsnObj.setState("state_idle", pEvent);
	//this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Created FSM-Session '+ JSON.stringify(pSsnObj));
	this.printSessionTable();
};

/**
 * Dispatches the event
 * @param pEvent
 * @param pSsnObj
 */
FsmManager.prototype.dispatchEvent = function(pEvent, pSsnObj) {
	
	this.executeFsm(pEvent,pSsnObj);
};
/**
 * Executes given Event in the session
 * @param pEvent
 * @param pSession
 */
FsmManager.prototype.executeFsm = function(pEvent, pSession) {
	var eventName = pEvent.getEventName();
	var handler = this.lookupEventHandler(eventName, pSession);
	if(handler) {
		this.logger.log(MODNAME, 'INFO_LEVEL_4', '['+pSession.sessionKey+'] state: '+pSession.state+', invoked event: '+eventName+': ');
		handler(this._SubsObj, pEvent, pSession);
	}
	else {
		this.logger.log(MODNAME, 'INFO_LEVEL_4', '['+pSession.sessionKey+'] state: '+pSession.state+', No event handler found for event: ' + eventName );
		
		this.logger.log(MODNAME, 'INFO_LEVEL_4', '['+pSession.sessionKey+'] state: '+pSession.state+', invoked event: event_notfound');
		var stateObj = pSession[pSession.state];
		handler = stateObj['event_notfound'];
		if(handler) {			
			handler(this._SubsObj, pEvent, pSession);
		}
	}
	
};

/**
 * Searches the eventhandler for the given event in the current state
 * @param pEventName
 * @param pSsnObj
 * @returns
 */
FsmManager.prototype.lookupEventHandler = function(pEventName, pSsnObj) {
	
	var curState = pSsnObj.state;
	var stateObj = pSsnObj[curState];

	if(stateObj){
		var handler = stateObj[pEventName];
		if(handler)
			return handler;		
	}

	return null;
};

/**
 * Process incoming Event
 * @param pEvent
 * @returns {Boolean}
 */
FsmManager.prototype.processEvent = function(pEvent) {
	//trim the module name for internal event
	var eventName = pEvent.getEventName();
	this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Started processing event: ['+eventName+']');
	
	var index = eventName.indexOf('.');
	if (index > 0) {
		var modName = eventName.substring(0,index).toUpperCase();
		//console.log(modName);
		//console.log(this._SubsObj._Config.NAME);
		if (modName === this._SubsObj._Config.NAME.toUpperCase()){
			eventName = eventName.substring(index+1);
			pEvent.setEventName(eventName);
		}
	}
	
	//console.log(pEvent.header.eventname);
	var fsmObj, ssnObj;
	var key = pEvent.getEventKey();
	var eventType = pEvent.getEventType();
	
	if (key) {
		if ('REQUEST' === eventType) {
			
			fsmObj = this.getFsmObjFromEvent(eventName);
			if (!fsmObj) {
				this.logger.log(MODNAME, 'INFO_LEVEL_4', 'FSM script not found for namespace of the event ['+pEvent.getEventName()+']');
				return false;
			}
			if (pEvent.reqFsmSessionId) {
				ssnObj = this.createFsmSession(key, pEvent.reqFsmSessionId, fsmObj);
			} else {
				ssnObj = this.createFsmSession(key, 0, fsmObj);
			}
			
			this.initializeFsmSession(pEvent, ssnObj);
			ssnObj.rcvdEvent = pEvent;
			
		} else if ('RESPONSE' === eventType) {			
			ssnObj = this._SessionTable.getItem(key);
			if (!ssnObj) {
				this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Failed to get session object for sessionid: '+ key);
				return false;
			}
			this.logger.log(MODNAME, 'INFO_LEVEL_4', '['+key+'] retrieved FsmSession object which is in state ['+ssnObj.state+']');			
		}
	}
	else {
		this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Invalid EventKey '+ key);
		return false;
	/*	if ('REQUEST' === eventType) {
			key = Math.random();
			var requestId = pEvent.getRequestId();
			fsmObj = this.getFsmObjFromEvent(pEvent);
			if (!fsmObj) {
				this.logger.log(MODNAME, 'INFO_LEVEL_4', 'FSM script not found for namespace of the event ['+pEvent.getEventName()+']');
				return false;
			}
			ssnObj = this.createFsmSession(key, requestId, fsmObj);
			this.initializeFsmSession(pEvent, ssnObj);
		} else if ('RESPONSE' === eventType) {
			key = pEvent.getRequestId();
			ssnObj = this._SessionTable.getItem(key);
			if (!ssnObj) {
				this.logger.log(MODNAME, 'INFO_LEVEL_4', 'Failed to get session object for sessionid: '+ key);
			}
		}
		*/
	}

	this.executeFsm(pEvent, ssnObj);
};

/**
 * prints the sessiontable
 */
FsmManager.prototype.printSessionTable = function () {
	this.logger.log(MODNAME, 'INFO_LEVEL_4', 'FSM-Table: '+JSON.stringify(this._SessionTable));
};
/**
 * prints the fsmtable
 */
FsmManager.prototype.printFsmTable = function () {
	this.logger.log(MODNAME, 'INFO_LEVEL_4', JSON.stringify(this._FsmTable));
};
/**
 * This function creates a child object that is inherited from the parent's prototype
 * @param p
 * @returns
 */
function inherit(p) {
    if (p === null) throw TypeError(); // p must be a non-null object
    if (Object.create)// If Object.create() is defined...
    	return Object.create(p);//    then just use it.
    var t = typeof p;                 // Otherwise do some more type checking
    if (t !== "object" && t !== "function") throw TypeError();
    function f() {};                  // Define a dummy constructor function.
    f.prototype = p;                  // Set its prototype property to p.
    return new f();                   // Use f() to create an "heir" of p.
}
